package io.noties.markwon.base.image.load

import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Handler
import io.noties.markwon.base.image.DrawableUtils.applyIntrinsicBounds

import android.os.Looper
import android.os.SystemClock
import android.util.Log
import com.gitee.wsl.ext.base.isTrue
import io.noties.markwon.base.image.AsyncDrawable
import io.noties.markwon.base.image.SchemeHandler
import io.noties.markwon.base.image.decoder.MediaDecoder
import io.noties.markwon.base.image.load.comust.ErrorHandler
import io.noties.markwon.base.image.load.comust.PlaceholderProvider
import java.io.IOException
import java.lang.IllegalStateException
import java.util.HashMap
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future

internal class AsyncDrawableLoaderImpl constructor(
    builder: AsyncDrawableLoaderBuilder,
    handler: Handler
) : AsyncDrawableLoader {
    private val executorService: ExecutorService
    private val schemeHandlers: Map<String, SchemeHandler>
    private val mediaDecoders: Map<String, MediaDecoder>
    private val defaultMediaDecoder: MediaDecoder
    private val placeholderProvider: PlaceholderProvider?
    private val errorHandler: ErrorHandler?
    private val handler: Handler

    // @since 4.0.0 use a hash-map with a AsyncDrawable as key for multiple requests
    //  for the same destination
    private val requests: MutableMap<AsyncDrawable, Future<*>> = HashMap(2)

    constructor(builder: AsyncDrawableLoaderBuilder) : this(builder, Handler(Looper.getMainLooper()))

    // @since 4.0.0
    init {
        executorService = builder.executorService!!
        schemeHandlers = builder.schemeHandlers
        mediaDecoders = builder.mediaDecoders
        defaultMediaDecoder = builder.defaultMediaDecoder!!
        placeholderProvider = builder.placeholderProvider
        errorHandler = builder.errorHandler
        this.handler = handler
    }

    override fun load(drawable: AsyncDrawable) {
        val future = requests[drawable]
        if (future == null) {
            requests[drawable] = execute(drawable)
        }
    }

    override fun cancel(drawable: AsyncDrawable) {
        val future = requests.remove(drawable)
        future?.cancel(true)
        handler.removeCallbacksAndMessages(drawable)
    }

    override fun placeholder(): Drawable? {
        return placeholderProvider?.providePlaceholder()
    }

    private fun execute(asyncDrawable: AsyncDrawable): Future<*> {
        return executorService.submit {
            val destination = asyncDrawable.destination
            val uri = Uri.parse(destination)
            var drawable: Drawable? = null
            try {
                val scheme = uri.scheme
                check(!scheme.isNullOrEmpty()) { "No scheme is found: $destination" }

                // obtain scheme handler
                val schemeHandler = schemeHandlers[scheme]
                if (schemeHandler != null) {

                    // handle scheme
                    val imageItem = schemeHandler.handle(destination, uri)

                    // if resulting imageItem needs further decoding -> proceed
                    if (imageItem.hasDecodingNeeded()) {
                        val withDecodingNeeded = imageItem.asWithDecodingNeeded

                        // @since 4.6.2 close input stream
                        try {
                            var mediaDecoder = mediaDecoders[withDecodingNeeded.contentType()]
                            if (mediaDecoder == null) {
                                mediaDecoder = defaultMediaDecoder
                            }
                            drawable = mediaDecoder.decode(
                                withDecodingNeeded.contentType(),
                                withDecodingNeeded.inputStream()
                            )
                                ?: // throw that no media decoder is found
                                        throw IllegalStateException("No media-decoder is found: $destination")
                        } finally {
                            try {
                                withDecodingNeeded.inputStream().close()
                            } catch (e: IOException) {
                                Log.e("MARKWON-IMAGE", "Error closing inputStream", e)
                            }
                        }
                    } else {
                        drawable = imageItem.asWithResult.result()
                    }
                } else {
                    // throw no scheme handler is available
                    throw IllegalStateException("No scheme-handler is found: $destination")
                }
            } catch (t: Throwable) {
                if (errorHandler != null) {
                    drawable = errorHandler.handleError(destination, t)
                } else {
                    // else simply log the error
                    Log.e("MARKWON-IMAGE", "Error loading image: $destination", t)
                }
            }
           /* val out = drawable

            // @since 4.0.0 apply intrinsic bounds (but only if they are empty)
            if (out != null) {
                val bounds = out.bounds
                if (bounds == null
                    || bounds.isEmpty
                ) {
                    applyIntrinsicBounds(out)
                }
            }
            handler.postAtTime({ // validate that
                // * request was not cancelled
                // * out-result is present
                // * async-drawable is attached
                val future = requests.remove(asyncDrawable)
                if (future != null && out != null && asyncDrawable.isAttached) {
                    asyncDrawable.setResult(out)
                }
            }, asyncDrawable, SystemClock.uptimeMillis())*/

            drawable?.let {
                it.bounds.isEmpty.isTrue {
                    applyIntrinsicBounds(it)
                }
                handler.postAtTime({ // validate that
                    // * request was not cancelled
                    // * out-result is present
                    // * async-drawable is attached
                    val future = requests.remove(asyncDrawable)
                    if (future != null && asyncDrawable.isAttached) {
                        asyncDrawable.setResult(it)
                    }
                }, asyncDrawable, SystemClock.uptimeMillis())
            }

        }
    }
}